from ipaddress import ip_address
from flask import Flask, request
from utils import respond, Cache
from os import urandom
from base64 import urlsafe_b64encode
from config import Config
from hashlib import md5
from jwt import encode, decode
from time import time
from functools import wraps


app = Flask(__name__)


def generate_token(byte_length=16):
    random_bytes = urandom(byte_length)
    token = urlsafe_b64encode(random_bytes).rstrip(b"=").decode("utf-8")
    return token


@app.route("/getTicket", methods=["GET"])
def get_ticket():
    device_id = request.args.get("deviceId")
    if not device_id:
        return respond(None, "缺少设备ID。", 201)
    elif device_id not in Config.DEVICES:
        return respond(None, "未知的设备ID。", 202)

    ticket = generate_token(24)
    Cache.set(ticket, device_id, 600)
    return respond({"ticket": ticket})


@app.route("/getToken", methods=["POST"])
def get_token():
    device_id, ticket, signature = (
        request.json.get("deviceId"),
        request.json.get("ticket"),
        request.json.get("signature"),
    )
    if not device_id or not ticket or not signature:
        return respond(None, "缺少参数。", 201)
    elif device_id not in Config.DEVICES or not Cache.get(ticket):
        return respond(None, "无效的deviceId或ticket。", 202)
    elif (
        signature
        != md5(
            (ticket + device_id + Config.DEVICES[device_id]).encode("utf-8")
        ).hexdigest()
    ):
        return respond(None, "无效的签名。", 203)

    payload = {
        "refresh_remaining": 1,
        "deviceId": device_id,
        "expiredTime": int(time()) + Config.JWT_EXPIRES,
    }
    return respond(
        {"token": encode(payload, Config.DEVICES[device_id], algorithm="HS256")}
    )


def auth_required(token_verification=True):
    def decorator(func):
        @wraps(func)
        def decorated(*args, **kwargs):
            device_id, token = (
                request.json.get("deviceId"),
                request.json.get("token"),
            )
            if not device_id or not token:
                return respond(None, "缺少参数。", 201)
            elif device_id not in Config.DEVICES:
                return respond(None, "无效的deviceId。", 202)
            elif token_verification:
                try:
                    data = decode(
                        token, Config.DEVICES[device_id], algorithms=["HS256"]
                    )
                    if data["expiredTime"] < int(time()):
                        return respond(None, "token已过期。", 206)
                except Exception:
                    return respond(None, "无效的token。", 204)
            return func(*args, **kwargs)

        return decorated

    return decorator


@app.route("/uploadData", methods=["POST"])
@auth_required()
def upload_data():
    data = request.json.get("data")
    # print(decode(token, Config.DEVICES[device_id], algorithms=["HS256"]))
    if not data:
        return respond(None, "缺少参数。", 201)

    try:
        if data["high"] <= data["low"] or data["time"] < int(time()) - 60:
            return respond(None, "无效的数据。", 205)
    except KeyError:
        return respond(None, "无效的数据。", 205)

    return respond({"receivedTime": int(time())}, "上传成功。")


@app.route("/refreshToken", methods=["POST"])
@auth_required(token_verification=False)
def refresh_token():
    device_id, token, signature = (
        request.json.get("deviceId"),
        request.json.get("token"),
        request.json.get("signature"),
    )

    if (
        signature
        != md5(
            (token + device_id + Config.DEVICES[device_id]).encode("utf-8")
        ).hexdigest()
    ):
        return respond(None, "无效的签名。", 203)
    old_token = decode(token, Config.DEVICES[device_id], algorithms=["HS256"])
    if old_token["refresh_remaining"] <= 0:
        return respond(None, "刷新次数已用尽。", 207)

    payload = {
        "refresh_remaining": old_token["refresh_remaining"] - 1,
        "deviceId": device_id,
        "expiredTime": int(time()) + Config.JWT_EXPIRES,
    }

    return respond(
        {"token": encode(payload, Config.DEVICES[device_id], algorithm="HS256")}
    )


app.run(host="0.0.0.0", debug=True)
